package net.cyue.web.easyquery.core;

import net.cyue.util.ObjectConverter;
import net.cyue.util.ReflectUtil;
import net.cyue.util.StringUtil;
import net.cyue.web.easyquery.core.config.ConfigException;
import net.cyue.web.easyquery.core.db.QueryType;
import net.cyue.web.easyquery.core.db.SQLType;
import net.cyue.web.easyquery.core.db.api.ISQLExecutor;
import net.cyue.web.easyquery.core.db.data.*;
import net.cyue.web.easyquery.core.db.util.SQLTypeDetector;
import net.cyue.web.easyquery.core.http.HTTPStatus;
import net.cyue.web.easyquery.core.http.adapter.DefaultHTTPServer;
import net.cyue.web.easyquery.core.http.api.IHTTPRouter;
import net.cyue.web.easyquery.core.http.api.IHTTPServer;
import net.cyue.web.easyquery.core.http.data.PathInfo;
import net.cyue.web.easyquery.core.http.data.QueryParameter;
import net.cyue.web.easyquery.core.http.exception.ParameterValidationException;
import net.cyue.web.easyquery.core.http.exception.SQLHandleException;
import net.cyue.web.easyquery.core.http.handler.DefaultWebExceptionHandler;
import net.cyue.web.easyquery.core.http.handler.DefaultWebResponseHandler;
import net.cyue.web.easyquery.core.http.handler.DefaultWebResultHandler;
import net.cyue.web.easyquery.core.http.handler.api.*;
import net.cyue.web.easyquery.core.http.util.ParameterValidator;
import net.cyue.web.easyquery.core.provider.InstanceProviderLoader;
import net.cyue.web.easyquery.core.provider.ServiceProviderLoader;
import net.cyue.web.easyquery.core.service.api.IService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * EasyQueryContext
 * @param <TContext> 原始服务上下文
 */
public class EasyQueryContext<TContext> {

    private static final String DEFAULT_CONTEXT_PATH = "/";
    private static final String DEFAULT_PAGE_PARAMETER = "page";
    private static final String DEFAULT_PAGE_SIZE_PARAMETER = "pageSize";
    private static final int DEFAULT_PAGE_SIZE = 10;

    private final Logger logger = LoggerFactory.getLogger(this.toString());
    private TContext serverContext;
    private ISQLExecutor sqlExecutor;
    private IHTTPServer httpServer;

    private String contextPath; /// 自己的 context path，与原始服务无关
    private final List<IWebRequestPreHandler> requestPreHandlerList = new ArrayList<>();
    private final Map<String, List<IWebRequestPreHandler>> requestPreHandlerMap = new HashMap<>();
    private IWebResultHandler resultHandler = new DefaultWebResultHandler();
    private IWebResponseHandler responseHandler = new DefaultWebResponseHandler();
    private IWebExceptionHandler exceptionHandler = new DefaultWebExceptionHandler();

    /**
     * 构造函数
     * @throws IOException IO异常
     * @throws ConfigException 配置异常
     */
    public EasyQueryContext()
        throws IOException, ConfigException
    {
        this.sqlExecutor = InstanceProviderLoader.load(ISQLExecutor.class).getInstance();
    }

    /**
     * 构造函数
     * @param serverContext 原始服务上下文
     * @throws IOException IO异常
     * @throws ConfigException 配置异常
     */
    public EasyQueryContext(TContext serverContext)
        throws IOException, ConfigException
    {
        this(serverContext, DEFAULT_CONTEXT_PATH);
    }

    /**
     * 构造函数
     * @param serverContext 原始服务上下文
     * @param contextPath 上下文路径
     * @throws IOException IO异常
     * @throws ConfigException 配置异常
     */
    public EasyQueryContext(TContext serverContext, String contextPath)
        throws IOException, ConfigException
    {
        this.setContextPath(contextPath);
        this.sqlExecutor = InstanceProviderLoader.load(ISQLExecutor.class).getInstance();
        this.setServerContext(serverContext);
    }

    public TContext getServerContext() {
        return this.serverContext;
    }
    public String getContextPath() { return this.contextPath; }

    /**
     * 设置 原始服务上下文
     * @param serverContext 原始服务上下文
     * @throws IOException IO异常
     * @throws ConfigException 配置异常
     */
    public void setServerContext(TContext serverContext)
        throws IOException, ConfigException
    {
        this.serverContext = serverContext;
        this.initHTTPComponents();
    }

    /**
     * 设置服务上下文路径
     * @param contextPath 服务上下文路径
     */
    public void setContextPath(String contextPath) {
        this.contextPath = PathInfo.normalizeURLPath(contextPath);
    }

    /**
     * 设置 SQL执行器
     * @param sqlExecutor SQL执行器
     */
    public void setSQLExecutor(ISQLExecutor sqlExecutor) {
        this.sqlExecutor = sqlExecutor;
    }

    /**
     * 设置 HTTP服务
     * @param httpServer HTTP服务
     */
    public void setHTTPServer(IHTTPServer httpServer) {
        this.httpServer = httpServer;
    }

    /**
     * 设置 HTTP路由器
     * @param httpRouter HTTP路由器
     */
    public void setHTTPRouter(IHTTPRouter httpRouter) {
        Class<?> clz = this.httpServer.getClass();
        while (true) {
            try {
                Field field = ReflectUtil.getField(clz, IHTTPRouter.class);
                ReflectUtil.setFieldValue(this.httpServer, field, httpRouter);
                break;
            } catch (NoSuchFieldException | InvocationTargetException e) {
                this.logger.warn(e.getMessage());
                clz = clz.getSuperclass();
            }
        }
    }

    /**
     * 添加 HTTP请求预处理器
     * @param preHandler HTTP请求预处理器
     */
    public void addRequestPreHandler(IWebRequestPreHandler preHandler) {
        if (preHandler == null) {
            this.logger.warn("添加的 HTTP 请求预处理器为空");
            return;
        }
        this.logger.info("添加全局请求预处理器 {}", preHandler.getClass().getName());
        this.requestPreHandlerList.add(preHandler);
    }
    /**
     * 添加 HTTP请求预处理器
     * @param pathInfo PathInfo
     * @param preHandler HTTP请求预处理器
     */
    public void addRequestPreHandler(PathInfo pathInfo, IWebRequestPreHandler preHandler) {
        if (pathInfo == null || StringUtil.isBlank(pathInfo.getAPIPath())) {
            this.logger.warn("添加的 HTTP 请求预处理器拦截路径为空");
            return;
        }
        this.addRequestPreHandler(pathInfo.getAPIPath(), preHandler);
    }
    /**
     * 添加 HTTP请求预处理器
     * @param path 拦截路径
     * @param preHandler HTTP请求预处理器
     */
    public void addRequestPreHandler(String path, IWebRequestPreHandler preHandler) {
        if (path == null || StringUtil.isBlank(path)) {
            this.logger.warn("添加的 HTTP 请求预处理器拦截路径为空");
            return;
        }
        if (preHandler == null) {
            this.logger.warn("添加的 HTTP 请求预处理器为空");
            return;
        }
        this.logger.info("为 {} 添加请求预处理器 {}", path, preHandler.getClass().getName());
        List<IWebRequestPreHandler> preHandlerList = this.requestPreHandlerMap.getOrDefault(path.trim(), new ArrayList<>());
        preHandlerList.add(preHandler);
        this.requestPreHandlerMap.put(PathInfo.normalizeURLPath(path), preHandlerList);
    }

    /**
     * 设置 HTTP请求预处理器
     * @param pathInfo PathInfo
     * @param preHandler HTTP请求预处理器
     */
    public void setRequestPreHandler(PathInfo pathInfo, IWebRequestPreHandler preHandler) {
        this.setRequestPreHandler(pathInfo.getAPIPath(), preHandler);
    }
    /**
     * 设置 HTTP请求预处理器
     * @param path 拦截路径
     * @param preHandler HTTP请求预处理器
     */
    public void setRequestPreHandler(String path, IWebRequestPreHandler preHandler) {
        String normalizationPath = PathInfo.normalizeURLPath(path);
        this.requestPreHandlerMap.put(normalizationPath, new ArrayList<>());
        this.addRequestPreHandler(normalizationPath, preHandler);
    }


    /**
     * 设置 Web结果处理器
     * @param resultHandler Web结果处理器
     */
    public void setResultHandler(IWebResultHandler resultHandler) {
        this.resultHandler = resultHandler;
    }

    /**
     * 设置 Web响应处理器
     * @param responseHandler Web响应处理器
     */
    public void setResponseHandler(IWebResponseHandler responseHandler) {
        this.responseHandler = responseHandler;
    }

    /**
     * 获取 Web异常处理器
     * @param exceptionHandler Web异常处理器
     */
    public void setExceptionHandler(IWebExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }


    private void initHTTPComponents()
        throws IOException
    {
        this.logger.info("初始化HTTP组件...");

        IHTTPServer tempServer;
        IHTTPRouter tempRouter = null;
        boolean isDefaultHTTPServer = false;
        boolean httpServerLoadByServiceProvider = false;

        this.logger.info("加载 HTTP Router...");
        try {
            this.logger.info("从 InstanceProvider 中获取 HTTP Router");
            tempRouter = InstanceProviderLoader.load(IHTTPRouter.class, this.serverContext).getInstance();
        } catch (ConfigException ce1) {
            this.logger.warn(ce1.getMessage());
            this.logger.info("从 ServiceProvider 中获取 HTTP Router");
            try {
                // 使用 ServerContext 作为 Prototype 获取 HTTPRouter Service
                IService<?> service = ServiceProviderLoader.load(this.serverContext).getService();
                if (service instanceof IHTTPRouter) {
                    tempRouter = (IHTTPRouter) service;
                }
            } catch (ConfigException ce2) {
                this.logger.warn(ce2.getMessage());
            }
        }

        this.logger.info("加载 HTTP Server...");
        try {
            this.logger.info("从 InstanceProvider 中获取 HTTP Server");
            tempServer = InstanceProviderLoader.load(IHTTPServer.class, tempRouter).getInstance();
        } catch (ConfigException ce1) {
            this.logger.warn(ce1.getMessage());
            this.logger.info("从 ServiceProvider 中获取 HTTP Server");
            // 使用 ServerContext 作为 Prototype 获取 HTTPServer Service
            try {
                IService<?> service = ServiceProviderLoader.load(this.serverContext).getService();
                if (service instanceof IHTTPServer) {
                    httpServerLoadByServiceProvider = true;
                    tempServer = (IHTTPServer) service;
                } else {
                    throw new ConfigException("Service 未实现 IHTTPServer 接口");
                }
            } catch (IOException | ConfigException ce2) {
                this.logger.info("使用默认 HTTP Server");
                isDefaultHTTPServer = true;
                tempServer = new DefaultHTTPServer(null);
            }
        }

        this.httpServer = tempServer;
        if (
            tempServer != null &&
            tempRouter != null &&
            (isDefaultHTTPServer || httpServerLoadByServiceProvider)
        ) {
            this.setHTTPRouter(tempRouter);
        }
    }


    /**
     * 注册网络接口
     * @param pathInfo 接口信息
     * @param queryWrapper 查询语句包装器
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerQuery(
        PathInfo pathInfo,
        QueryWrapper queryWrapper
    ) throws ConfigException {
        return this.registerQuery(
            pathInfo,
            DEFAULT_PAGE_PARAMETER,
            DEFAULT_PAGE_SIZE_PARAMETER,
            queryWrapper,
            QueryType.QUERY_LIST
        );
    }

    /**
     * 注册网络接口
     * @param pathInfo 接口信息
     * @param queryWrapper 查询语句包装器
     * @param queryType 查询类型
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerQuery(
        PathInfo pathInfo,
        String pageParameter,
        String pageSizeParameter,
        QueryWrapper queryWrapper,
        QueryType queryType
    ) throws ConfigException {
        if (queryWrapper == null) {
            this.logger.warn("SQL Query Wrapper 不能为空");
            return false;
        }
        return this.registerWebInterface(
            pathInfo,
            pageParameter,
            pageSizeParameter,
            queryWrapper.toSQL(),
            SQLType.QUERY,
            queryType
        );
    }

    /**
     * 注册分页查询接口
     * @param pathInfo 接口信息
     * @param pageParameter 分页参数名
     * @param pageSizeParameter 分页大小参数名
     * @param queryWrapper 查询语句包装器
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerPageQuery(
        PathInfo pathInfo,
        String pageParameter,
        String pageSizeParameter,
        QueryWrapper queryWrapper
    ) throws ConfigException {
        if (queryWrapper == null) {
            this.logger.warn("SQL Query Wrapper 不能为空");
            return false;
        }
        return this.registerPageQuery(
            pathInfo,
            pageParameter,
            pageSizeParameter,
            queryWrapper.toSQL()
        );
    }

    /**
     * 注册分页查询接口
     * @param pathInfo 接口信息
     * @param pageParameter 分页参数名
     * @param pageSizeParameter 分页大小参数名
     * @param sql 用于查询的预编译SQL
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerPageQuery(
        PathInfo pathInfo,
        String pageParameter,
        String pageSizeParameter,
        String sql
    ) throws ConfigException {
        return this.registerPageQuery(
            pathInfo,
            pageParameter,
            pageSizeParameter,
            (request, _m) -> new SQLInfo(sql, SQLType.QUERY)
        );
    }

    /**
     * 注册分页查询接口
     * @param pathInfo 接口信息
     * @param pageParameter 分页参数名
     * @param pageSizeParameter 分页大小参数名
     * @param sqlHandler 构造用于执行的预编译SQL的处理器
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerPageQuery(
        PathInfo pathInfo,
        String pageParameter,
        String pageSizeParameter,
        IExecutionSQLHandler sqlHandler
    ) throws ConfigException {
        return this.registerWebInterface(
            pathInfo,
            (request, parameterMap) -> {
                SQLInfo sqlInfo = sqlHandler.handle(request, parameterMap);
                if (sqlInfo == null) {
                    return null;
                }
                if (sqlInfo.getSQLType() != SQLType.QUERY) {
                    throw new IllegalArgumentException("SQLInfo is not a query SQL");
                }
                if (
                    sqlInfo instanceof SQLPageInfo &&
                    ((SQLPageInfo) sqlInfo).getQueryPage() != null
                ) {
                    return sqlInfo;
                }

                Integer page = request.getParameter(pageParameter, Integer.class);
                Integer pageSize = request.getParameter(pageSizeParameter, Integer.class);
                if (page == null || page < 1) {
                    page = 1;
                }
                if (pageSize == null || pageSize < 1) {
                    pageSize = DEFAULT_PAGE_SIZE;
                }
                return new SQLPageInfo(sqlInfo.getSql(), new QueryPage(page, pageSize, true));
            }
        );
    }

    /**
     * 注册网络接口
     * @param pathInfo 接口信息
     * @param sql 用于查询的预编译SQL
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerWebInterface(
        PathInfo pathInfo,
        String sql
    ) throws ConfigException {
        return this.registerWebInterface(
            pathInfo,
            DEFAULT_PAGE_PARAMETER,
            DEFAULT_PAGE_SIZE_PARAMETER,
            sql,
            null,
            QueryType.QUERY_LIST
        );
    }

    /**
     * 注册网络接口
     * @param pathInfo 接口信息
     * @param sql 用于查询的预编译SQL
     * @param queryType 查询类型
     * @param sqlType SQL类型
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerWebInterface(
        PathInfo pathInfo,
        String pageParameter,
        String pageSizeParameter,
        String sql,
        SQLType sqlType,
        QueryType queryType
    ) throws ConfigException {
        // 避免每次调用接口时都要判断 SQL 类型
        if (sqlType == null || sqlType == SQLType.UNKNOWN) {
            sqlType = SQLType.fromSQL(sql);
        }
        if (sqlType == SQLType.UNKNOWN) {
            this.logger.warn("SQLType 未定义");
        }
        if (queryType == null) {
            this.logger.warn("QueryType 未定义，默认使用 {}", QueryType.QUERY_LIST);
            queryType = QueryType.QUERY_LIST;
        }
        QueryType finalQueryType = queryType;
        SQLType finalSqlType = sqlType;
        return this.registerWebInterface(
            pathInfo,
            (request, parameterMap) -> {
                if (finalQueryType == QueryType.QUERY_PAGE || finalQueryType == QueryType.PAGE) {
                    Integer page = ObjectConverter.convert(parameterMap.get(pageParameter), Integer.class);
                    Integer pageSize = ObjectConverter.convert(parameterMap.get(pageSizeParameter), Integer.class);
                    if (page == null || page < 1) {
                        page = 1;
                    }
                    if (pageSize == null || pageSize < 1) {
                        pageSize = DEFAULT_PAGE_SIZE;
                    }
                    return new SQLPageInfo(sql, new QueryPage(page, pageSize, true));
                }
                return new SQLInfo(sql, finalSqlType, finalQueryType);
            }
        );
    }

    public boolean registerWebInterface(
        PathInfo pathInfo,
        SQLInfo sqlInfo
    ) throws ConfigException {
        return this.registerWebInterface(
            pathInfo,
            (_req, _map) -> sqlInfo
        );
    }

    /**
     * 注册网路接口
     * @param pathInfo 接口信息
     * @param sqlHandler 构造用于执行的预编译SQL的处理器
     * @throws ConfigException 配置异常
     * @return 注册结果
     */
    public boolean registerWebInterface(
        PathInfo pathInfo,
        IExecutionSQLHandler sqlHandler
    ) throws ConfigException {
        if (this.sqlExecutor == null) {
            this.logger.warn("SQLExecutor 为空");
            throw new ConfigException("SQLExecutor 未初始化，请先注册服务");
        }
        if (this.httpServer == null) {
            this.logger.warn("HTTPServer 为空");
            throw new ConfigException("HTTPServer 未初始化，请先注册服务");
        }

        // context path 为 本程序的，同原始服务无关，故映射 FullPath
        // 也许 router 对 path 做一定处理
        pathInfo.setContextPath(this.contextPath);
        this.logger.info("注册接口：{}", pathInfo.getFullPath());
        return this.httpServer.use(pathInfo.getFullPath(), (request, response) -> {

            // 全局预处理器
            for (IWebRequestPreHandler preHandler : this.requestPreHandlerList) {
                if (!preHandler.preHandle(request, response)) {
                    this.logger.info(
                        "请求 {} 被 {} 拦截",
                        pathInfo.getFullPath(),
                        preHandler
                    );
                    response.status(HTTPStatus.BAD_REQUEST);
                    return;
                }
            }
            // 指定路径预处理器
            if (
                this.requestPreHandlerMap.containsKey(pathInfo.getAPIPath()) ||
                this.requestPreHandlerMap.containsKey(pathInfo.getFullPath())
            ) {
                List<IWebRequestPreHandler> preHandlerList = this.requestPreHandlerMap.get(pathInfo.getAPIPath());
                if (preHandlerList == null || preHandlerList.isEmpty()) {
                    preHandlerList = this.requestPreHandlerMap.get(pathInfo.getFullPath());
                }
                for (IWebRequestPreHandler preHandler : preHandlerList) {
                    if (!preHandler.preHandle(request, response)) {
                        this.logger.info(
                            "请求 {} 被 {} 拦截",
                            pathInfo.getFullPath(),
                            preHandler
                        );
                        response.status(HTTPStatus.BAD_REQUEST);
                        return;
                    }
                }
            }

            // 处理 请求参数
            Map<String, Object> parameterMap = pathInfo.parsePathParameterMap(request.uri()); // 路径参数
            for (String parameterName : pathInfo.getQueryParameterNames()) { // 查询参数
                String[] parameters = request.getParameterValues(parameterName);
                if (parameters == null || parameters.length == 0) {
                    continue;
                }
                if (parameters.length == 1) {
                    parameterMap.put(parameterName, parameters[0]);
                    continue;
                }
                parameterMap.put(parameterName, parameters);
            }
            // body 参数
            String contentType = request.getHeader("Content-Type");
            if (!StringUtil.isBlank(contentType) && contentType.contains("json")) {
                parameterMap.putAll(request.body());
            }

            // 验证参数
            for (QueryParameter parameter : pathInfo.getQueryParameterList()) {
                String name = parameter.getName();
                Object value = parameterMap.get(name);
                // 如何 value 为空，则赋值默认值
                if (value == null) {
                    value = parameter.getDefaultValue();
                    parameterMap.put(name, parameter.getDefaultValue());
                }
                ParameterValidator.ValidationResult result =
                    ParameterValidator.validate(parameter, value);
                if (!result.isValid()) {
                    this.logger.warn("接口 {} 参数 {} 验证失败：{}", pathInfo.getFullPath(), name, result.getMessage());
                    response.status(HTTPStatus.BAD_REQUEST);
                    this.exceptionHandler.handle(
                        new ParameterValidationException(result.getMessage()),
                        response
                    );
                    return;
                }
                // 正常无问题
            }

            // 获取 sql
            SQLInfo sqlInfo;
            String sql;
            try {
                sqlInfo = sqlHandler.handle(request, parameterMap);
                if (sqlInfo == null) {
                    response.status(HTTPStatus.BAD_REQUEST);
                    throw new SQLHandleException("SQLInfo is null");
                }
                if (sqlInfo.getSQLType() == SQLType.UNKNOWN) {
                    response.status(HTTPStatus.BAD_REQUEST);
                    throw new SQLHandleException("SQLType is unknown");
                }
                sql = sqlInfo.getSql();
                if (StringUtil.isBlank(sql)) {
                    response.status(HTTPStatus.BAD_REQUEST);
                    throw new SQLHandleException("SQL is null");
                }
            } catch (SQLHandleException e) {
                this.logger.warn("接口 {} 获取 SQL 失败", pathInfo.getFullPath(), e);
                return;
            }

            // 执行 sql，返回结果
            try {
                Object result = null;
                // 查询
                if (sqlInfo.getSQLType() == SQLType.QUERY) {
                    if (sqlInfo.getQueryType() == QueryType.QUERY_LIST || sqlInfo.getQueryType() == QueryType.LIST) {
                        List<Map<String, Object>> list = this.sqlExecutor.query(sql, parameterMap);
                        result = this.resultHandler.handleQuery(list);
                    } else if (sqlInfo.getQueryType() == QueryType.QUERY_ONE || sqlInfo.getQueryType() == QueryType.ONE) {
                        Map<String, Object> one = this.sqlExecutor.queryOne(sql, parameterMap);
                        result = this.resultHandler.handleQueryOne(one);
                    } else if (sqlInfo.getQueryType() == QueryType.QUERY_PAGE || sqlInfo.getQueryType() == QueryType.PAGE) {
                        QueryPage queryPage = ((SQLPageInfo) sqlInfo).getQueryPage();
                        PageQueryResult pageResult = this.sqlExecutor.queryPage(sql, parameterMap, queryPage);
                        result = this.resultHandler.handlePageQuery(pageResult);
                    }
                }
                // 更新
                else if (sqlInfo.getSQLType() == SQLType.UPDATE) {
                    int updateCount = this.sqlExecutor.update(sql, parameterMap);
                    result = this.resultHandler.handleUpdate(updateCount);
                }
                this.responseHandler.handle(response, result);
            } catch (Exception e) {
                if (this.exceptionHandler == null) {
                    throw new RuntimeException(e);
                }
                this.exceptionHandler.handle(e, response);
            }
        });
    }
}
